﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SimpleService.Authentication;

namespace SimpleService.Authorize
{
    public class RolesBasedAuthorizationService: IAuthorizationService
    {
        private IAuthorizeSettings _settings;
        private IRoleService _roleService;
        private static readonly string[] AnonymousRole = new[] { "Anonymous" };
        private static readonly string[] AuthenticatedRole = new[] { "Authenticated" };
        public RolesBasedAuthorizationService(IAuthorizeSettings settings, IRoleService roleService)
        {
            _settings = settings;
            _roleService = roleService;
        }
        public bool TryCheckAccess(Permission permission, IUser user)
        {
            bool granted = false;
            if (user!= null)
            {
                if (!String.IsNullOrEmpty(_settings.SuperUser) &&
                       String.Equals(user.UserName, _settings.SuperUser, StringComparison.Ordinal))
                {
                    granted = true;
                }
            }
            if (!granted)
            {
                var grantingNames = PermissionNames(permission, Enumerable.Empty<string>()).Distinct().ToArray();
                IEnumerable<string> rolesToExamine;
                if (user == null)
                {
                    rolesToExamine = AnonymousRole;
                }
                else if (user.Roles != null && user.Roles.Count > 0)
                {
                    rolesToExamine = user.Roles;
                }
                else
                {
                    rolesToExamine = AuthenticatedRole;
                }
                foreach (var role in rolesToExamine)
                {
                    foreach (var permissionName in _roleService.GetPermissionsForRoleByName(role))
                    {
                        string possessedName = permissionName;
                        if (grantingNames.Any(grantingName => String.Equals(possessedName, grantingName, StringComparison.OrdinalIgnoreCase)))
                        {
                            granted = true;
                        }
                        if (granted)
                            break;
                    }
                    if (granted)
                        break;
                }
            }
            return granted;
        }

        private static IEnumerable<string> PermissionNames(Permission permission, IEnumerable<string> stack)
        {
            // the given name is tested
            yield return permission.Name;

            // iterate implied permissions to grant, it present
            if (permission.ImpliedBy != null && permission.ImpliedBy.Any())
            {
                foreach (var impliedBy in permission.ImpliedBy)
                {
                    // avoid potential recursion
                    if (stack.Contains(impliedBy.Name))
                        continue;

                    // otherwise accumulate the implied permission names recursively
                    foreach (var impliedName in PermissionNames(impliedBy, stack.Concat(new[] { permission.Name })))
                    {
                        yield return impliedName;
                    }
                }
            }
        }
    }
}
